﻿'Copyright 2009 by Henry J. Tolino Jr.
'Licensed for use under the GNU General Public License version 2
'Contributions, corrections and the occasional gratuity welcomed!
'Higuchi.Net.Mail from the C# Mail library posted on Codeplex: http://csharpmail.codeplex.com/
'Used under the GNU General Public License version 2: http://csharpmail.codeplex.com/license
Imports System.Net.Mail
Imports Higuchi.Net.Mail
Imports Higuchi.Net.Pop3
Imports Higuchi.Net.Pop3.Command
Imports Microsoft.MediaCenter.TV
Imports System.IO
Imports System.Collections.Generic
Imports System.Xml
Imports System.Xml.Serialization
Imports System
Imports Microsoft.MediaCenter.TV.Scheduling
Imports Microsoft.MediaCenter.TV.Epg
Imports System.Configuration
Imports System.Threading
Public Class RemoteWizardWindowsService
    Private gClient As Pop3Client
    Private gMessage As Pop3Message = Nothing
    Private gMessageList As New List(Of Pop3Message)()
    Private strLogon As String = ConfigurationManager.AppSettings("GMail_Logon")
    Private strPassword As String = ConfigurationManager.AppSettings("GMail_Password")
    Private strFrom1Keyword As String = ConfigurationManager.AppSettings("From1_Keyword").ToUpper
    Private strFrom2Keyword As String = ConfigurationManager.AppSettings("From2_Keyword").ToUpper
    Private strFrom3Keyword As String = ConfigurationManager.AppSettings("From3_Keyword").ToUpper
    Private strFrom4Keyword As String = ConfigurationManager.AppSettings("From4_Keyword").ToUpper
    Private strProgramKeyword As String = ConfigurationManager.AppSettings("Program_Keyword").ToUpper
    Private strTVPIExtension As String = ConfigurationManager.AppSettings("TVPI_Extension").ToUpper
    Private strPollingInterval As String = ConfigurationManager.AppSettings("Polling_Interval")
    Private strSourceDirectory As String = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) & "\RemoteWizard\"
    Private iPollingInterval As Integer = 0
    Private RemoteWizardTimer As Threading.Timer
    Private tCallback As System.Threading.TimerCallback
    Public Sub New()
        InitializeComponent()
    End Sub
    Protected Overrides Sub OnStart(ByVal args() As String)
        Integer.TryParse(strPollingInterval, iPollingInterval)
        'convert to milliseconds
        iPollingInterval = 1000 * iPollingInterval
        'if zero, set the default to 1 minute
        If iPollingInterval = 0 Then iPollingInterval = 60000
        tCallback = New System.Threading.TimerCallback(AddressOf OnTimedEvent)
        RemoteWizardTimer = New Threading.Timer(tCallback, Nothing, iPollingInterval, iPollingInterval)
    End Sub
    Protected Overrides Sub OnStop()
        RemoteWizardTimer.Change(Threading.Timeout.Infinite, Threading.Timeout.Infinite)
        RemoteWizardTimer.Dispose()
    End Sub
    Protected Overrides Sub OnPause()
        MyBase.OnPause()
        RemoteWizardTimer.Change(Threading.Timeout.Infinite, Threading.Timeout.Infinite)
    End Sub
    Protected Overrides Sub OnContinue()
        RemoteWizardTimer.Change(iPollingInterval, iPollingInterval)
    End Sub
    Private Sub OnTimedEvent()
        RemoteWizardTimer.Change(Threading.Timeout.Infinite, Threading.Timeout.Infinite)
        Dim alTVIP As ArrayList = GetCurrentScheduleRequests()
        If alTVIP.Count = 0 Then
            WriteLogFile("RemoteWizardService.Log", "Nothing to schedule")
        Else
            Dim rrRecordingRequest As RecordingRequest
            Dim strRequestedBy As String = String.Empty
            Dim strResults As String = "The following recording requests have been brocessed by the RemoteWizard:" & vbCrLf & vbCrLf
            For Each rrRecordingRequest In alTVIP
                strResults += rrRecordingRequest.Results & vbCrLf & vbCrLf
                strRequestedBy = rrRecordingRequest.RequestedBy
            Next
            strResults += vbCrLf & "We just thought you should know."
            If strRequestedBy Is String.Empty Then
                WriteLogFile("RemoteWizardService.Log", "No reply-to address!")
            Else
                SendGmailConfirmation(strRequestedBy, strResults)
            End If
        End If
        RemoteWizardTimer.Change(iPollingInterval, iPollingInterval)
    End Sub
    Private Function GetCurrentScheduleRequests() As ArrayList
        Dim alTVPI As New ArrayList
        If Not LogonToGMail() Then
            WriteLogFile("RemoteWizardService.Log", "Can't log on to GMail!")
            Return alTVPI
        End If
        WriteLogFile("RemoteWizardService.Log", "Connected!")

        Dim iMessageCount As Integer = gClient.GetTotalMessageCount()
        If iMessageCount = 0 Then
            WriteLogFile("RemoteWizardService.Log", "No new messages")
            Return alTVPI
        End If

        Dim iCount, jCount As Integer
        For iCount = 1 To iMessageCount
            gMessage = gClient.GetMessage(iCount)
            'check if it's from someone on the approved list
            If gMessage.From.ToUpper.Contains(strFrom1Keyword) Or gMessage.From.ToUpper.Contains(strFrom2Keyword) Or gMessage.From.ToUpper.Contains(strFrom3Keyword) Or gMessage.From.ToUpper.Contains(strFrom4Keyword) Then
                'check to see if it's a record request
                If gMessage.Subject.ToUpper.Contains(strProgramKeyword) Then
                    'check to see if there's one (or more) 'program.tvpi' files attached
                    Dim lPop3 As List(Of Pop3Content) = Pop3Message.GetAllContents(gMessage)
                    If lPop3.Count > 0 Then
                        For jCount = 0 To (lPop3.Count - 1)
                            If lPop3(jCount).FileName.ToUpper.Contains(strTVPIExtension) Then
                                'save the attachment to a string
                                Dim sTVPI As New IO.MemoryStream
                                lPop3(jCount).DecodeData(sTVPI, False)
                                sTVPI.Position = 0
                                Dim srTVPI As New StreamReader(sTVPI)
                                Dim strTVPI As String = srTVPI.ReadToEnd
                                sTVPI.Close()
                                sTVPI.Dispose()

                                'convert the string to an XML document
                                Dim xDoc As New XmlDocument()
                                xDoc.LoadXml(strTVPI)
                                Dim m_nodelist As XmlNodeList
                                Dim m_node As XmlNode
                                m_nodelist = xDoc.SelectNodes("/tv-program-info/program")
                                'can have more than one record request in a document
                                For Each m_node In m_nodelist
                                    Dim myRecordingRequest As RecordingRequest = ParseRecordingRequest(gMessage.From, m_node)
                                    myRecordingRequest.Success = ScheduleARecording(myRecordingRequest)
                                    alTVPI.Add(myRecordingRequest)
                                Next
                            End If
                        Next
                    End If
                End If
            End If
        Next
        If Not gClient.State = Pop3ConnectionState.Disconnected Then gClient.Close()
        Return alTVPI
    End Function
    Private Function ParseRecordingRequest(ByVal strRequestedBy As String, ByVal m_node As XmlNode) As RecordingRequest
        Dim myRecordingRequest As New RecordingRequest
        Dim strRawStartDate As String = m_node.SelectSingleNode("start-date").InnerText '20091101
        Dim strRawStartTime As String = m_node.SelectSingleNode("start-time").InnerText '03:00
        Dim strStartDateTime As String = strRawStartDate.Substring(4, 2) & "/" & strRawStartDate.Substring(6, 2) & "/" & strRawStartDate.Substring(0, 4) & " " & strRawStartTime & ":00"

        myRecordingRequest.StartDateTime = DateTime.Parse(strStartDateTime).AddHours(-5)
        myRecordingRequest.RequestedBy = strRequestedBy
        myRecordingRequest.Title = m_node.SelectSingleNode("program-title").InnerText
        myRecordingRequest.Channel = m_node.SelectSingleNode("rf-channel").InnerText
        myRecordingRequest.Duration = ConvertDurationToMinutes(m_node.SelectSingleNode("duration").InnerText) '01:30

        Return myRecordingRequest
    End Function
    Private Function LogonToGMail() As Boolean
        'be sure to open Port 995 for POP3 access to gMail
        Dim bReturn As Boolean = False
        gClient = New Pop3Client(strLogon, strPassword, "pop.gmail.com")
        With gClient
            .AuthenticateMode = Pop3AuthenticateMode.Pop
            .Port = 995
            .Ssl = True
            .ReceiveTimeout = 10000
        End With

        Try
            bReturn = gClient.Authenticate()
        Catch ex As Exception
            WriteLogFile("RemoteWizardService.Log", "Error opening gMail client: " & ex.Message)
        End Try

        Return bReturn
    End Function
    Private Function SendGmailConfirmation(ByVal strReturnAddress As String, ByVal strBody As String) As Boolean
        'be sure to open Port 587 for SMTP access to gMail
        Dim bResults As Boolean = False
        Dim gClient As New SmtpClient("smtp.gmail.com")
        Dim gResponse As New MailMessage()

        With gClient
            .Port = 587
            .Credentials = New System.Net.NetworkCredential(strLogon, strPassword)
            .EnableSsl = True
        End With

        With gResponse
            .From = New Net.Mail.MailAddress(strLogon)
            .To.Add(strReturnAddress)
            .Subject = "RemoteWizard Schedule Confirmation"
            .Body = strBody
        End With

        Try
            gClient.Send(gResponse)
            bResults = True
        Catch ex As SmtpException
            WriteLogFile("RemoteWizardService.Log", "Error sending email confirmation: " & ex.Message)
        End Try
        Return bResults
    End Function
    Private Function CreateC2R(ByVal strTitle As String, ByVal strChannelNumber As String, ByVal dtStartDateTime As DateTime, ByVal iDuration As Integer) As String
        Dim strResults As String = String.Empty
        strResults += "<?xml version=""1.0"" encoding=""utf-8"" ?> "
        strResults += "<clickToRecord xmlns=""urn:schemas-microsoft-com:ehome:clicktorecord""> "
        strResults += "    <body> "
        strResults += "        <metadata> "
        strResults += "            <description>Scheduled by your Remote Wizard</description> "
        strResults += "        </metadata> "
        strResults += "        <programRecord isRecurring=""false"" allowAlternateServices=""false"" programDuration=""" & iDuration.ToString & """> "
        'strResults += "            <program>"
        'strResults += "                <key field=""urn:schemas-microsoft-com:ehome:epg:program#title"" match=""contains"">" & strTitle & "</key> "
        'strResults += "            </program> "
        strResults += "            <airing searchSpan=""10""> "
        strResults += "                <key field=""urn:schemas-microsoft-com:ehome:epg:airing#starttime"">" & dtStartDateTime.ToString("yyyy-MM-ddTHH:mm:ss") & "-05:00</key> "
        strResults += "            </airing> "
        strResults += "            <service> "
        strResults += "                <key field=""urn:schemas-microsoft-com:ehome:epg:service#mappedChannelNumber"">" & strChannelNumber & "</key>"
        strResults += "            </service>"
        strResults += "        </programRecord>"
        strResults += "    </body> "
        strResults += "</clickToRecord> "
        Return strResults
    End Function
    Public Function ScheduleARecording(ByRef myRecordingRequest As RecordingRequest) As Boolean
        If myRecordingRequest.Duration = 0 Then Return False
        Dim bResults As Boolean = False
        Dim strC2R As String = CreateC2R(myRecordingRequest.Title, myRecordingRequest.Channel, myRecordingRequest.StartDateTime, myRecordingRequest.Duration)
        Dim xrC2R As XmlReader

        Dim scheduler As EventSchedule
        Dim request As ScheduleRequest = Nothing
        Dim result As CreateScheduleRequestResult

        Try
            xrC2R = XmlReader.Create(New StringReader(strC2R))
            scheduler = New EventSchedule()
            result = scheduler.CreateScheduleRequest(xrC2R, ConflictResolutionPolicy.AllowConflict, "Scheduled by Remote Wizard", request)
        Catch ex As Exception
            WriteLogFile("RemoteWizardService.Log", "Error scheduling " & myRecordingRequest.Title & " on " & myRecordingRequest.Channel & " at " & myRecordingRequest.StartDateTime.ToString)
            WriteLogFile("RemoteWizardService.Log", "Exception from EventSchedule.CreateScheduleRequest(): " & ex.Message)
        Finally
            xrC2R.Close()
        End Try

        If request Is Nothing Then
            WriteLogFile("RemoteWizardService.Log", "Unknown error scheduling " & myRecordingRequest.Title & " on " & myRecordingRequest.Channel & " at " & myRecordingRequest.StartDateTime.ToString)
            WriteLogFile("RemoteWizardService.Log", "No recording scheduled!")
        Else
            Select Case result
                Case CreateScheduleRequestResult.AllProgramsInPast
                    myRecordingRequest.Results = "Unable to schedule " & myRecordingRequest.Title & ": program is already over."
                Case CreateScheduleRequestResult.AlreadyScheduled
                    myRecordingRequest.Results = myRecordingRequest.Title & "was already scheduled."
                Case CreateScheduleRequestResult.ConflictNotScheduled
                    myRecordingRequest.Results = "A scheduling conflict prevented adding " & myRecordingRequest.Title & "to your queue."
                Case CreateScheduleRequestResult.ExceededMaxRequests
                    myRecordingRequest.Results = "Unable to schedule " & myRecordingRequest.Title & ": you've exceeded your maximum recording requests."
                Case CreateScheduleRequestResult.Expired
                    myRecordingRequest.Results = "Unable to schedule " & myRecordingRequest.Title & ": request expired."
                Case CreateScheduleRequestResult.GuideNotConfigured
                    myRecordingRequest.Results = "Unable to schedule " & myRecordingRequest.Title & ": your program guide is not configured."
                Case CreateScheduleRequestResult.NoConfiguredResource
                    myRecordingRequest.Results = "Unable to schedule " & myRecordingRequest.Title & ": a required Media Center resource is not configured properly."
                Case CreateScheduleRequestResult.NotFound
                    myRecordingRequest.Results = "Unable to schedule " & myRecordingRequest.Title & ": program not found."
                Case CreateScheduleRequestResult.Scheduled
                    myRecordingRequest.Results = myRecordingRequest.Title & " has been successfully added to your recording schedule but conflicts with another recording."
                Case CreateScheduleRequestResult.ScheduledNoConflict
                    myRecordingRequest.Results = myRecordingRequest.Title & " has been successfully added to your recording schedule with no conflicts"
                    bResults = True
                Case Else
                    myRecordingRequest.Results = "Unknown response from CreateScheduleRequest scheduling " & myRecordingRequest.Title & " on " & myRecordingRequest.Channel & " at " & myRecordingRequest.StartDateTime.ToString
            End Select
            WriteLogFile("RemoteWizardService.Log", myRecordingRequest.Results)
        End If
        WriteLogFile("RemoteWizardService.Log", "ScheduleAProgram: End")
        Return bResults
    End Function
    Private Sub WriteLogFile(ByVal strLogName As String, ByVal strLogMessage As String)
        Dim swLogWriter As New StreamWriter(strSourceDirectory & strLogName, True)
        swLogWriter.WriteLine(DateTime.Now.ToString & ": " & strLogMessage)
        swLogWriter.Close()
    End Sub
    Private Function ConvertDurationToMinutes(ByVal strDuration As String) As Integer
        If Not strDuration.Contains(":") Then Return 0
        Dim strResults() As String = strDuration.Split(":")
        Dim iHours As Integer = 0
        Dim iMinutes As Integer = 0
        Integer.TryParse(strResults(0), iHours)
        Integer.TryParse(strResults(1), iMinutes)
        Return ((iHours * 60) + iMinutes)
    End Function
    Private Sub BuildEmailResponse(ByVal myRecordingRequest As RecordingRequest)

    End Sub
End Class
Public Class RecordingRequest
    Private strRequestedBy As String
    Private strTitle As String
    Private strChannel As String
    Private dtStartDateTime As DateTime
    Private iDuration As Integer
    Private strResults As String
    Private bSuccess As Boolean
    Public Property RequestedBy() As String
        Get
            Return strRequestedBy
        End Get
        Set(ByVal value As String)
            strRequestedBy = value
        End Set
    End Property
    Public Property Title() As String
        Get
            Return strTitle
        End Get
        Set(ByVal value As String)
            strTitle = value
        End Set
    End Property
    Public Property Channel() As String
        Get
            Return strChannel
        End Get
        Set(ByVal value As String)
            strChannel = value
        End Set
    End Property
    Public Property StartDateTime() As DateTime
        Get
            Return dtStartDateTime
        End Get
        Set(ByVal value As DateTime)
            dtStartDateTime = value
        End Set
    End Property
    Public Property Duration() As Integer
        Get
            Return iDuration
        End Get
        Set(ByVal value As Integer)
            iDuration = value
        End Set
    End Property
    Public Property Results()
        Get
            Return strResults
        End Get
        Set(ByVal value)
            strResults = value
        End Set
    End Property
    Public Property Success() As Boolean
        Get
            Return bSuccess
        End Get
        Set(ByVal value As Boolean)
            bSuccess = value
        End Set
    End Property
End Class